﻿package we3d.core 
{
	import we3d.core.transform.Transform3d;
	import we3d.core.Object3d;
	import we3d.math.Matrix3d;
	import we3d.math.Vector3d;
	import we3d.mesh.Vertex;
	
	/** 
	* Every scene have one Camera3d. Use the scene.cam property to access the Camera in a scene. 
	*/
	public class Camera3d extends Object3d 
	{
		public function Camera3d (w:Number=550, h:Number=400, fov:Number=0.7, near:Number=5, far:Number=5000) {
			initProjection(fov, near, far, w, h)
		}
		
		/** 
		* @private
		* Read Only, use <code>cam.width</code> setter to set the width 
		*/
		public var _width:Number;
		/** 
		* @private
		* Read Only, use <code>cam.height</code> setter to set the height 
		*/
		public var _height:Number;
		/** 
		* @private
		* height * .5 
		*/
		public var s:Number;
		/** 
		* @private
		* width * .5 
		*/
		public var t:Number;
		/** 
		* @private
		* Projection Matrix 
		*/
		public var pr:Matrix3d=new Matrix3d();
		/**
		* @private
		*/
		public var cgv:Matrix3d=new Matrix3d();
		/**
		* @private
		*/
		public var _nearClipping:Number;
		/**
		* @private
		*/
		public var _farClipping :Number;
		/**
		* @private
		*/
		public var _fieldOfView:Number;
		
		private static var sfc:int=0;
		
		/**
		* Set the width of the screen
		*/
		public function set width (val:int) :void {
			initProjection(_fieldOfView, _nearClipping, _farClipping, val); 
		}
		public function get width () :int { 	return _width;	}
		
		/**
		* Set the height of the screen
		*/
		public function set height (val:int) :void {
			initProjection(_fieldOfView, _nearClipping, _farClipping, _width, val); 
		}
		public function get height () :int { 	return _height;	}
		
		/** 
		* Vertical Field Of View in radians
		*/
		public function get fov () :Number { return _fieldOfView; }
		public function set fov (r:Number) :void { 
			initProjection(r, _nearClipping, _farClipping); 
		}
		
		/** 
		* Horizontal Field Of View in radians
		*/
		public function get fovH () :Number {
			return 2*Math.atan(_width*Math.tan(_fieldOfView/2)/_height);					  
		}
		public function set fovH (f:Number) :void {
			fov = 2*Math.atan(_height*Math.tan(f/2)/_width);					  
		}
		
		/** 
		* Near clipping plane
		*/
		public function get nearClipping () :Number { return _nearClipping; }
		public function set nearClipping (n:Number) :void { 
			initProjection(_fieldOfView, n, _farClipping); 
		}
		/** 
		* Far clipping plane
		*/
		public function get farClipping () :Number { return _farClipping; }
		public function set farClipping (n:Number) :void { 
			initProjection(_fieldOfView, _nearClipping, n); 
		}
		
		/**
		* Tranlsates a 3d point to screen space and stores the numeric values in the vertex v and out.
		* 
		* v.wx, v.wy, v.wz     - camera space 
		* v.sx, v.sy           - screen space 
		* out.x, out.y, out.z  - object space (optional)
		* 
		* @param	v the vertex to translate
		* @param	cam the camera for projection
		* @param	obj if obj is not null, the point is first transformed with object transformation
		* @param	optional stores the object transformed point from object transformation
		*/
		public function projectPoint (v:Vertex, obj:Object3d=null, out:Vector3d=null) :void {
			
			var x:Number = v.x;
			var y:Number = v.y;
			var z:Number = v.z;
			
			if(obj != null) 
			{
				var mt:Matrix3d = obj.transform.gv;
				
				x = mt.a*x + mt.e*y + mt.i*z + mt.m;
				y = mt.b*x + mt.f*y + mt.j*z + mt.n;
				z = mt.c*x + mt.g*y + mt.k*z + mt.o;
				
				if(out != null) {
					out.x = x;
					out.y = y;
					out.z = z;
				}
			}
			
			v.wx = cgv.a*x + cgv.e*y + cgv.i*z + cgv.m;
			v.wy = cgv.b*x + cgv.f*y + cgv.j*z + cgv.n;
			v.wz = cgv.c*x + cgv.g*y + cgv.k*z + cgv.o + _nearClipping;
			
			if(v.wz>0) {
				v.sy = s - v.wy/v.wz * s;
				v.sx = t + v.wx/v.wz * t;
			}else{
				v.sy = s - v.wy * s;
				v.sx = t + v.wx * t;
			}
		}
		
		/** 
		* Initializes the projection matrix
		* @param	fov		Field Of View Angle in radian
		* @param	near	Near Clipping Plane
		* @param	far		Far Clipping Plane
		* @param	w		width of the camera screen
		* @param	h		height of the camera screen
		*/
		public function initProjection (_fov:Number, near:Number=-1, far:Number=-1, w:Number=-1, h:Number=-1) :void {
			if(_fov <= 0) _fov = 45*Math.PI/180;
			_fieldOfView = _fov;
			
			if(near != -1) _nearClipping = near;
			if(far != -1) _farClipping = far;
			
			if(w != -1) {
				_width = w;
				t = _width * .5;
			}
			if(h != -1) {
				_height = h;
				s = _height * .5;
			}
			
			var aspect:Number = _height/_width;
			var h:Number = Math.cos(_fieldOfView/2)/Math.sin(_fieldOfView/2);
			var w:Number = aspect * h;
			var q:Number = _farClipping/(_farClipping-_nearClipping);
			
			pr.initialize();
			pr.a = w;
			pr.f = h;
			pr.k = q;
			pr.o = -q * _nearClipping;
		}
		
		/**
		* @private
		*/
		public function initCamFrame (f:Number) :void {
			/*
			if(transform._parent != null) {
				var p:Transform3d = transform._parent;
				var objs:Array = [];
				while(p) {
					objs.push(p);
					p = p._parent;
				}
				for(var i:int=objs.length-1; i>=0; i--)  objs[i].initFrame(f);
			}*/
			
			Transform3d.FCounter = sfc++;
			
			if(frameInit) transform.initFrame(f);
			
			var gv:Matrix3d = transform.gv;
			
			cgv.a = gv.a*pr.a;	cgv.b = gv.e*pr.f;	cgv.c = gv.i*pr.k;	
			cgv.e = gv.b*pr.a;	cgv.f = gv.f*pr.f;	cgv.g = gv.j*pr.k;	
			cgv.i = gv.c*pr.a;	cgv.j = gv.g*pr.f;	cgv.k = gv.k*pr.k;
			
			var x:Number = gv.m;
			var y:Number = gv.n;
			var z:Number = gv.o;
			
			cgv.m = -(x * gv.a + y * gv.b + z * gv.c) * pr.a;
			cgv.n = -(x * gv.e + y * gv.f + z * gv.g) * pr.f;
			cgv.o = -(x * gv.i + y * gv.j + z * gv.k) * pr.k + pr.o;
		}
		
	}
}